/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.image;

import javax.microedition.lcdui.Image;

import com.aphrodite.util.math.MathUtil;

/**
 * Image transformation routines<br/>
 * <p>
 * The efficiency of alpha processing and rgb data extraction is platform
 * independent
 * </p>
 * 
 * @author kentelt
 * 
 */
public class ImageTransformation {
	private static int rotatedH;

	private static int rotatedW;

	/**
	 * Crops an image from the points x and y to the given width and height.
	 * 
	 * @param image
	 *            source
	 * @param x
	 *            point of image to start crop
	 * @param y
	 *            point of image to start crop
	 * @param width
	 *            of crop
	 * @param height
	 *            of crop
	 * @return
	 */
	public static Image cropImage(Image image, int x, int y, int width, int height) {
		int[] src = ImageUtil.getImageRGB(image, image.getWidth(), image.getHeight());
		int[] dst = new int[width * height];// .getData();
		cropImage(src, dst, image.getWidth(), x, y, width, height);
		return Image.createRGBImage(dst, width, height, true);
	}

	// /**
	// * Rotate an image with angle thats multiples of 90
	// *
	// * @param image
	// * @param angle
	// * @return
	// * @throws Exception
	// *
	// * Image
	// */
	// public static final Image rotateImage(Image image, int angle) {
	// // ############ tested by $t3p#3n on 29-july #################//
	// if (angle == 0) {
	// return image;
	// } else if (angle != 180 && angle != 90 && angle != 270) {
	// try {
	//
	// } catch (Exception e) {
	// System.out.println("Invalid  rotation angle");
	// return null;
	// }
	// }
	//
	// int width = image.getWidth();
	// int height = image.getHeight();
	//
	// int[] rowData = new int[width];
	// int[] rotatedData = new int[width * height];
	//
	// int rotatedIndex = 0;
	//
	// for (int i = 0; i < height; i++) {
	// image.getRGB(rowData, 0, width, 0, i, width, 1);
	//
	// for (int j = 0; j < width; j++) {
	// rotatedIndex = angle == 90 ? (height - i - 1) + j * height : (angle ==
	// 270 ? i + height
	// * (width - j - 1) : width * height - (i * width + j) - 1);
	//
	// rotatedData[rotatedIndex] = rowData[j];
	// }
	// }
	//
	// if (angle == 90 || angle == 270) {
	// return Image.createRGBImage(rotatedData, height, width, true);
	// } else {
	// return Image.createRGBImage(rotatedData, width, height, true);
	// }
	// }

	/**
	 * 
	 * @param srcARGB
	 * @param destARGB
	 *            int[width of crop*height of crop]
	 * @param srcImageWidth
	 *            width of srcARGB
	 * @param x
	 *            coordinate to begin crop
	 * @param y
	 *            coordinate to begin crop
	 * @param width
	 *            of crop
	 * @param height
	 *            of crop
	 */
	public static final void cropImage(int[] srcARGB, int[] destARGB, int srcImageWidth, int x, int y, int width,
			int height) {
		for (int i = 0; i < height; i++) {
			System.arraycopy(srcARGB, (i + y) * srcImageWidth + x, destARGB, i * width, width);
		}
	}

	/**
	 * Returns the new height for the given degree. The new heigth symbols the
	 * heigth for an rotated rgb data array with the same degree rotation.
	 * 
	 * @param degree
	 *            the degree value of the rotation.
	 * @param width
	 *            the width of the rgb source.
	 * @param heigth
	 *            the heigth of the rgb source
	 * @param degreeCos
	 *            the cosine of the degree value: Math.cos(Math.PI*degree/180)
	 * @param degreeSin
	 *            the sine of the degree value: Math.sin(Math.PI*degree/180)
	 * @return the new height of the rgb data.
	 */
	private static final int getRotatedHeight(int degree, int width, int heigth, double degreeCos, double degreeSin) {
		if (degree == -90 || degree == 90 || degree == 270 || degree == -270) {
			return width;
		} else if (degree == 360 || degree == 180 || degree == 0) {
			return heigth;
		}
		long pointY1 = MathUtil.round(0 * degreeSin + 0 * degreeCos);
		long pointY2 = MathUtil.round(width * degreeSin + 0 * degreeCos);
		long pointY3 = MathUtil.round(0 * degreeSin + heigth * degreeCos);
		long pointY4 = MathUtil.round(width * degreeSin + heigth * degreeCos);
		long minY = pointY1;
		if (pointY2 < minY) {
			minY = pointY2;
		}
		if (pointY3 < minY) {
			minY = pointY3;
		}
		if (pointY4 < minY) {
			minY = pointY4;
		}
		long maxY = pointY1;
		if (pointY2 > maxY) {
			maxY = pointY2;
		}
		if (pointY3 > maxY) {
			maxY = pointY3;
		}
		if (pointY4 > maxY) {
			maxY = pointY4;
		}
		return (int) (maxY - minY);
	}

	/**
	 * Returns the new width for the given degree. The new width symbols the
	 * width for an rotated rgb data array with the same degree rotation.
	 * 
	 * @param degree
	 *            the degree value of the rotation.
	 * @param width
	 *            the width of the rgb source.
	 * @param heigth
	 *            the heigth of the rgb source
	 * @param degreeCos
	 *            the cosine of the degree value: Math.cos(Math.PI*degree/180)
	 * @param degreeSin
	 *            the sine of the degree value: Math.sin(Math.PI*degree/180)
	 * @return the new width of the rgb data.
	 */
	private static final int getRotatedWidth(int degree, int width, int heigth, double degreeCos, double degreeSin) {
		if (degree == -90 || degree == 90 || degree == 270 || degree == -270) {
			return heigth;
		} else if (degree == 360 || degree == 180 || degree == 0) {
			return width;
		}
		long pointX1 = 0; // MathUtil.round(0 * degreeCos - 0 * degreeSin);
		long pointX2 = MathUtil.round(width * degreeCos); //MathUtil.round(width
		// * degreeCos - 0
		// *degreeSin);
		long pointX3 = MathUtil.round(-heigth * degreeSin); // MathUtil.round(0
		// *degreeCos -
		// heigth
		// *degreeSin);
		long pointX4 = MathUtil.round(width * degreeCos - heigth * degreeSin);
		long minX = pointX1;
		if (pointX2 < minX) {
			minX = pointX2;
		}
		if (pointX3 < minX) {
			minX = pointX3;
		}
		if (pointX4 < minX) {
			minX = pointX4;
		}
		long maxX = pointX1;
		if (pointX2 > maxX) {
			maxX = pointX2;
		}
		if (pointX3 > maxX) {
			maxX = pointX3;
		}
		if (pointX4 > maxX) {
			maxX = pointX4;
		}
		return (int) (maxX - minX);
	}

	/**
	 * 
	 * @param image
	 * @param angle
	 *            -360 to 360
	 * @return
	 */
	public static Image rotate(Image image, int angle) {
		int[] argb = ImageUtil.getImageRGB(image, image.getWidth(), image.getHeight());
		argb = rotate(argb, image.getWidth(), image.getHeight(), angle, image.getWidth() / 2, image.getHeight() / 2);
		return Image.createRGBImage(argb, rotatedW, rotatedH, true);
	}

	/**
	 * Rotates the given RGB data image.
	 * 
	 * @param image
	 *            the RGB data image that is going to be rotated. Warning: the
	 *            RGB data might get replaced
	 * @param angle
	 *            the angle for the rotation in degrees (-360..0..360)
	 * @param referenceX
	 *            the horizontal reference point for the rotation
	 * @param referenceY
	 *            the vertical reference point for the rotation
	 */
	private static int[] rotate(int[] rgbData, int width, int height, int angle, int referenceX, int referenceY) {
		double degreeCos = Math.cos(com.aphrodite.util.math.MathEngine.PI * angle / 180);
		double degreeSin = Math.sin(com.aphrodite.util.math.MathEngine.PI * angle / 180);
		int rotatedWidth = getRotatedWidth(angle, width, height, degreeCos, degreeSin);
		int rotatedHeight = getRotatedHeight(angle, width, height, degreeCos, degreeSin);
		int[] rotatedRgbData = new int[rotatedWidth * rotatedHeight];
		rotate(rgbData, width, height, referenceX, referenceY, 0x00FFFFFF, degreeCos, degreeSin, rotatedRgbData,
				rotatedWidth, rotatedHeight);
		rotatedW = rotatedWidth;
		rotatedH = rotatedHeight;
		return rotatedRgbData;
		// image.setRgbData(rotatedRgbData, rotatedWidth);
		// image.setWidth(rotatedWidth);
		// image.setHeight(rotatedHeight);
	}

	/**
	 * Rotates the source RGB data and stores it within the target RGB data
	 * array.
	 * 
	 * @param sourceRgbData
	 *            the rgb data to be rotated.
	 * @param width
	 *            the width of the source rgb data.
	 * @param height
	 *            the heigth of the source rgb data.
	 * @param referenceX
	 *            the x position for the center of rotation.
	 * @param referenceY
	 *            the y position for the center of rotation.
	 * @param backgroundColor
	 *            the ARGB color used for the background
	 * @param degreeCos
	 *            the cosine of the degree value: Math.cos(Math.PI*degree/180)
	 * @param degreeSin
	 *            the sine of the degree value: Math.sin(Math.PI*degree/180)
	 * @param rotatedRGB
	 *            the RGB data array for storing the rotated pixel data
	 * @param rotatedWidth
	 *            the width of the rotated rgb data
	 * @param rotatedHeight
	 *            the height of the rotated rgb data
	 */
	private static void rotate(int[] sourceRgbData, int width, int height, int referenceX, int referenceY,
			int backgroundColor, double degreeCos, double degreeSin, int[] rotatedRGB, int rotatedWidth,
			int rotatedHeight) {
		int halfOfWidth = width / 2;
		int halfOfHeigth = height / 2;
		int refX, refY, newX, newY, sumXY;
		for (int x = 0; x < rotatedWidth; x++) {
			for (int y = 0; y < rotatedHeight; y++) {
				refX = x - referenceX;
				refY = y - referenceY;
				newX = (int) (refX * degreeCos + refY * degreeSin);
				newY = (int) (refY * degreeCos - refX * degreeSin);
				newX += halfOfWidth;
				newY += halfOfHeigth;
				if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
					sumXY = newX + newY * width;
					rotatedRGB[x + y * rotatedWidth] = sourceRgbData[sumXY];
				} else {
					rotatedRGB[x + y * rotatedWidth] = backgroundColor;
				}
			}
		}
	}

	public static Image rotateSimple(Image image, int degrees) {
		System.out.println("rotate simple");
		int w = image.getWidth();
		int h = image.getHeight();
		int[] argb = ImageUtil.getImageRGB(image, w, h);
		int dstW = (degrees == 90 || degrees == 270) ? h : w;
		int dstH = (degrees == 90 || degrees == 270) ? w : h;
		int[] target = new int[dstW * dstH];
		rotateSimple(argb, target, w, h, degrees);
		return Image.createRGBImage(target, dstW, dstH, true);
	}

	/**
	 * Rotates the given source RGB array
	 * 
	 * @param source
	 *            the source RGB data
	 * @param target
	 *            the target RGB data
	 * @param w
	 *            the width of the source data
	 * @param h
	 *            the height of the source data
	 * @param degrees
	 *            either 90, 180 or 270 degrees
	 * @throws IllegalArgumentException
	 *             when the degrees are not either 90, 180 or 270
	 */
	public static void rotateSimple(int[] source, int[] target, int w, int h, int degrees) {

		switch (degrees) {
		case 90:
			for (int row = 0; row < h; row++) {
				for (int col = 0; col < w; col++) {
					target[col * h + ((h - 1) - row)] = source[row * w + col];
				}
			}
			break;
		case 180:
			for (int row = 0; row < h; row++) {
				for (int col = 0; col < w; col++) {
					target[((h - 1) - row) * w + ((w - 1) - col)] = source[row * w + col];
				}
			}
			break;
		case 270:
			for (int row = 0; row < h; row++) {
				for (int col = 0; col < w; col++) {
					target[((w - 1) - col) * h + row] = source[row * w + col];
				}
			}
			break;
		default:
			throw new IllegalArgumentException();

		}
	}

	private ImageTransformation() {}

}
